/*
 * @ProjectName: 编程学习
 * @Copyright:   2019 HangZhou Ashe Dev, Ltd. All Right Reserved.
 * @address:     https://yiyuery.github.io/NoteBooks/
 * @date:        2020/3/14 5:42 下午
 * @description: 本内容仅限于编程技术学习使用，转发请注明出处.
 */
package com.example.redis.controller;

import com.example.redis.lock.IRedisLock;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

/**
 * <p>
 *
 * </p>
 *
 * @author Helios
 * @date 2020/3/14 5:42 下午
 */
@RequestMapping
@RestController
@Slf4j
public class OrderRestController {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private IRedisLock redisLock;

    private Object lock = new Object();

    private final String productKey = "stockLock";

    /**
     * 查询当前库存
     *
     * @return
     */
    @RequestMapping("goods/current/stock")
    public String currentStock() {
        //查询库存
        Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        return "request success" + " stock : " + stock;
    }

    /**
     * 下单：线程不安全
     *
     * @return
     */
    @RequestMapping("goods/v1/stock")
    public String order() {
        //查询库存
        Integer stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
        if (stock > 0) {
            stock = stock - 1;
            stringRedisTemplate.opsForValue().set("stock", stock + "");
            log.info("order success and stock -1");
        } else {
            log.error("order failed!");
        }
        return "request success" + " stock : " + stock;
    }

    /**
     * 下单：synchronized 加锁，单机锁
     *
     * @return
     */
    @RequestMapping("goods/v2/stock")
    public String localOrderLock() {
        //查询库存
        Integer stock = 0;
        synchronized (lock) {
            stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                stock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", stock + "");
                log.info("order success and stock -1");
            } else {
                log.error("order failed!");
            }
        }
        return "request success" + " stock : " + stock;
    }

    /**
     * 下单：setnx 加锁，分布式锁
     *
     * @return
     */
    @RequestMapping("goods/v3/stock")
    public String setnx() {
        //redis setnx
        //Boolean stockLock = stringRedisTemplate.opsForValue().setIfAbsent(productKey, "xxx");
        //避免服务器异常，导致锁未被删除，设置超时时间
        //stringRedisTemplate.expire(productKey, 30, TimeUnit.SECONDS);
        //上面的非原子性，无法确保锁的超时时间一定会被设置
        Boolean stockLock = stringRedisTemplate.opsForValue().setIfAbsent(productKey, "xxx",30, TimeUnit.SECONDS);
        if (!stockLock) {
            return "error";
        }
        Integer stock = 0;
        //处理运行异常，避免死锁，确保系统正常运行场景下每次锁被删除
        try {
            //查询库存 redis.get
            stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                // 更新库存
                stock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", stock + "");
                log.info("order success and stock -1");
            } else {
                log.error("order failed!");
            }
        } finally {
            //库存减完后删除锁，确保其他线程不被一致阻塞
            stringRedisTemplate.delete(productKey);
        }
        return "request success" + " stock : " + stock;
    }

    /**
     * 下单：setnx 加锁，锁控制和业务逻辑解耦
     *
     * @return
     */
    @RequestMapping("goods/v4/stock")
    public String setnx2() {
        //业务解耦
        if (!redisLock.tryLock(productKey,30, TimeUnit.SECONDS)) {
            //此处返回 error，实际业务中一般不希望直接返回错误给用户，而是让用户等待一段时间，因此需要实现阻塞锁，见 RedisLockImpl
            return "error";
        }
        Integer stock = 0;
        //处理运行异常，避免死锁，确保系统正常运行场景下每次锁被删除
        try {
            //查询库存 redis.get
            stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                // 更新库存
                stock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", stock + "");
                log.info("order success and stock -1");
            } else {
                log.error("order failed!");
            }
        } finally {
            //库存减完后删除锁，确保其他线程不被一致阻塞
            redisLock. releaseLock(productKey);
        }
        return "request success" + " stock : " + stock;
    }

    /**
     * 下单：setnx 加锁，锁控制和业务逻辑解耦
     *
     * @return
     */
    @RequestMapping("goods/v5/stock")
    public String redissonLock() {
        //Redisson Lock
        RLock lock = redissonClient.getLock(productKey);
        lock.lock();
        Integer stock = 0;
        //处理运行异常，避免死锁，确保系统正常运行场景下每次锁被删除
        try {
            //查询库存 redis.get
            stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                // 更新库存
                stock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", stock + "");
                log.info("order success and stock -1");
            } else {
                log.error("order failed!");
            }
        } finally {
            //库存减完后删除锁，确保其他线程不被一致阻塞
            lock.unlock();
        }
        return "request success" + " stock : " + stock;
    }
}
